接續前篇最後問題,得把透過擴充方法帶入的方法參數也 Handle
要完成這件事情,就必須將擴充方法中的方法在經過處理之前就先完成 Handle
所以利用 interface 將IChain<>
與IChainAwaiter<>
加以擴充
讓擴充方法也可以使用 Handler 並且在 Handle 結束後在做轉換變成原本建立下一個 Chain 所需要的參數
public class FuncParser<T>
{
private readonly IHandler _handler;
private readonly Action<Func<T>> _callback;
internal FuncParser(IHandler handler,Action<Func<T>> callback)
{
_handler = handler;
_callback = callback;
}
public void Parse<TLast>(TLast arg,Func<TLast, T> func)
=> _callback(Handle(func,arg));
private Func<T> Handle<TLast>(Func<TLast, T> func,TLast arg)
{
_handler.Handle(func);
return () => func(arg);
}
}
public class TaskParser<T>
{
private readonly IHandler _handler;
private readonly Action<Task<T>> _callback;
internal TaskParser(IHandler handler,Action<Task<T>> callback)
{
_handler = handler;
_callback = callback;
}
public void Parse<TLast>(TLast arg, Func<TLast, Task<T>> func)
{
_handler.Handle(func);
_callback(func(arg));
}
public void Parse<TLast>(Task<TLast> arg, Func<TLast, T> func)
{
_handler.Handle(func);
_callback(Task.Run(async () => func(await arg)));
}
public void Parse<TLast>(Task<TLast> arg, Func<TLast, Task<T>> func)
{
_handler.Handle(func);
_callback(Task.Run(async () => await func(await arg)));
}
}
Parser
的目的在於轉換方法參數成下一個 Chain 建立的建構參數
而其中的Callback
的目的是因為這個方法因為是由擴充方法內呼叫
但希望取得結果的是ChainFactory.Create
,所以透過這樣的Callback來將結果推送給它
所以ChainFactory會變成這樣
public class ChainFactory : IChainFactory
{
public IChain<T> Create<T>(Action<FuncParser<T>> config)
{
Func<T> factory = null;
config(new FuncParser<T>(_handler,func => factory = func));
return new LazyChain<T>(this, Handle(factory));
}
public IChainAwaiter<T> Create<T>(Action<TaskParser<T>> config)
{
Task<T> task = null;
config(new TaskParser<T>(_handler,t => task = t));
return new ChainAwaiter<T>(this, Handle(task));
}
//....其他省略
}
這裡讓Parser在ChainFactory中建構而不是擴充方法的目的
是為了後續也許可以讓Parser也透過DI來做為建構方式
所以擴充方法也做相對應的調整
public static class FluentExtensions
{
public static IChain<TNext> Then<T, TNext>(
this IChain<T> chain,
Func<T, TNext> func)
=> chain.Factory.Create<TNext>(p => p.Parse(chain.Result, func));
public static IChainAwaiter<TNext> Then<T, TNext>(
this IChain<T> chain,
Func<T, Task<TNext>> next)
=> chain.Factory.Create<TNext>(p => p.Parse(chain.Result, next));
public static IChainAwaiter<TNext> Then<T, TNext>(
this IChainAwaiter<T> chain,
Func<T, Task<TNext>> next)
=> chain.Factory.Create<TNext>(p => p.Parse(chain.Result, next));
public static IChainAwaiter<TNext> Then<T, TNext>(
this IChainAwaiter<T> chain,
Func<T, TNext> next)
=> chain.Factory.Create<TNext>(p => p.Parse(chain.Result, next));
}
Action<>
此時的目的則是用於將擴充方法中的兩個參數帶入的才實際建立Parser
來套用的方法
利用建構 Parser 的時候帶入Callback的內容,當config方法參數被執行完成後,所需要的參數也已經Set完成
而這也其實就是把非同步全部黏起來寫成一排的情境
如此一來所有的功能都已經連接在一起了,最後我們來打造這些功能的使用法和入口
public class ResultContext
{
private readonly Func<object> _getter;
public ResultContext(Func<object> getter)
{
_getter = getter;
}
public object Result => _getter();
public Type Type => Result.GetType();
}
public class CallerContext
{
public CallerContext(object caller)
{
Target = caller;
}
public object Target { get; }
public Type Type => Target.GetType();
public Type From => Type.GenericTypeArguments.First();
public Type To => Type.GenericTypeArguments.Last();
public Delegate Delegate => (Delegate)Target;
public MethodInfo CallerInfo => Delegate.Method;
public string CallerName => CallerInfo.Name;
}
ResultContext
是用於Handle單一次結果的Context
CallerContext
是用於Handle方法參數的Context
所以在把所有 Handle 方法的參數改為帶入這種強行別的類別
public class ChainFactory : IChainFactory
{
private Func<T> Handle<T>(Func<T> factory)
{
var result = factory();
_handler.Handle(new ResultContext(() => result));
return () => result;
}
private Task<T> Handle<T>(Task<T> task)
{
_handler.Handle(new ResultContext(() => task.Result));
return task;
}
//其他省略....
}
public class FuncParser<T>
{
private Func<T> Handle<TLast>(Func<TLast, T> func,TLast arg)
{
_handler.Handle(new CallerContext(func));
return () => func(arg);
}
//其他省略
}
如此一來IHandler.Handle<>()
方法就可以不需要漫無目的地去尋找對象
所以在註冊服務時,額外加上定義方法
public class ChainBuilder
{
public ChainBuilder(IServiceCollection services)
{
Services = services;
}
public IServiceCollection Services { get; }
}
public static class ServiceCollectionExtensions
{
public static ChainBuilder AddChain(this IServiceCollection services)
{
services.AddTransient<Chain>()
.AddTransient<IChainFactory, ChainFactory>()
.AddSingleton<IHandler, Handler>();
return new ChainBuilder(services);
}
public static ChainBuilder AddResultHandler(
this ChainBuilder builder,
Action<ResultContext> handler)
{
builder.Services.AddSingleton<IHandler<ResultContext>>(new Handler<ResultContext>(handler));
return builder;
}
public static ChainBuilder AddCallerHandler(
this ChainBuilder builder,
Action<CallerContext> handler)
{
builder.Services.AddSingleton<IHandler<CallerContext>>(new Handler<CallerContext>(handler));
return builder;
}
}
這樣就大功告成了,而且修改前的測試也沒有任何錯誤,這樣的功能可以達到什麼樣的效果呢?
透過以下的設定,跑出來的結果大概是這樣
var logwriter = new StringWriter();
new ServiceCollection()
.AddChain()
.AddCallerHandler(ctx => logwriter.WriteAsync($"use {ctx.From.Name} invoke {ctx.CallerName} will get {ctx.To.Name} "))
.AddResultHandler(ctx => logwriter.WriteLineAsync($"is {ctx.Result}"))
//----中略並輸出 logwriter的字串如下
use TextWriter invoke WriteOne will get TextWriter is 1
use TextWriter invoke WriteTwoAsync will get Task`1 is 12
use TextWriter invoke WriteOne will get TextWriter is 121
use TextWriter invoke WriteTwoAsync will get Task`1 is 1212
這是我用TextWriter作為Chain的起始物件依序以兩個方法來交互執行後所得到的Log
雖然還有不少可以補強的部分,但是我們的目的也達到了
而最後,為什麼我們需要 Clean Code , 為何我們需要更清楚的命名規範
這樣的 Log 我想也有機會成為其中一個值得努力的理由吧
與其多說不如直接 Run 看看吧
很感謝您如此有毅力看到這裡,因為以我所接觸到的人中,這些內容可能有如古代魔法書一般的 Magic~~
前面說抽象也不是毫無根據的,如果這些內容您都有辦法看懂其內容並且已經融入你的開發習慣
那至少在我的觀點,你也是個厲害的強者,雖然我覺得在台灣似乎並不喜歡這種類型的開發者
如果您仍然懵懵懂懂地看到這裡,仍有許多疑惑,我想也許我大概也無法解釋的清楚
因為再怎麼解釋,還是那麼的抽象,~~ 飄~~~ ~~
不過能看到這裡的我相信都多少一定是技術愛好者或是對這些內容深感興趣的人
歡迎以任何方式交流,個人的交流觀點如下: